/*
 * File     : Pointer.java
 * Created  : 1 May 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.type;

import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.logging.Level;

import com.googlecode.dni.DirectNativeInterface;
import com.googlecode.dni.FixedSize;
import com.googlecode.dni.Library;
import com.googlecode.dni.Symbol;
import com.googlecode.dni.Warning;
import com.googlecode.dni.internal.DniInternal;
import com.googlecode.dni.internal.NativeUnsafeAccess;
import com.googlecode.dni.type.nativetype.NativeTypes;


/**
 * <p>
 *  Encapsulates a raw pointer.
 * </p>
 * <p>
 *  Use of this class is generally only necessary when the C type is
 *  <code>void&nbsp;*</code>, or it is a type that cannot be represented by this
 *  toolkit.
 * </p>
 *
 * @author Matthew Wilson
 */
public final class Pointer
{

    /**
     * <p>
     *  The platform-specific null pointer.
     * </p>
     * <p>
     *  Contrary to popular opinion, the null pointer is not necessarily the
     *  zero memory address; however, in C, the literal value "<code>0</code>"
     *  <em>is</em> the null pointer.
     * </p>
     */
    public static final Pointer NULL;

    private static final ByteOrder NATIVE_ORDER = ByteOrder.nativeOrder();

    static
    {
        // TODO support architectures -- if there are any left -- that have non-zero null pointers
        NULL = new Pointer( 0L, false );
    }

    private final long address;

    /**
     * @param address
     *            the address
     */
    public Pointer( final long address )
    {
        if ( DniInternal.DEBUG_MODE && address == 0L )
        {
            // LOGGER.warn( "Creating pointer with explicit zero is bad form, unless you have memory mapped at zero" );
        }
        this.address = address;
    }

    /**
     * @param address
     *            the address
     * @param mustBeFalse
     *            ignored value to give access to a different constructor
     */
    private Pointer( final long address, final boolean mustBeFalse )
    {
        // bypass the main constructor's warning
        this.address = address;

        if ( mustBeFalse )
        {
            throw new UnsupportedOperationException();
        }
    }

    /**
     * <p>
     *  Obtains the address in memory.
     * </p>
     *
     * @return the address
     */
    public long getAddress()
    {
        return this.address;
    }

    /**
     * <p>
     *  Determines whether this pointer is NULL.
     * </p>
     *
     * @return <code>true</code> iff NULL
     */
    public boolean isNull()
    {
        return this.address == 0L;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals( final Object obj )
    {
        if ( obj == null || obj.getClass() != getClass() )
        {
            return false;
        }
        if ( obj == this )
        {
            return true;
        }
        Pointer other = (Pointer) obj;
        return this.address == other.address;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode()
    {
        return (int) ( this.address ^ this.address >>> 32 );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString()
    {
        return "0x" + Long.toHexString( this.address ); //$NON-NLS-1$
    }

    /**
     * Creates a new pointer from this pointer with a given offset
     *
     * @param offset
     *            the offset (in bytes)
     * @return the new pointer
     */
    public Pointer add( final long offset )
    {
        if ( offset == 0 )
        {
            return this;
        }

        if ( DniInternal.DEBUG_MODE && this.address == 0L )
        {
            DniInternal.LOGGER.log( Level.WARNING, "Adding offset to NULL pointer", new Warning() );
        }
        return new Pointer( this.address + offset );
    }

    /**
     * <p>
     *  Creates a new buffer from this pointer with the given size.
     * </p>
     * <p>
     *  No check is made to verify whether there is actually valid memory
     *  mapped at the address range.
     * </p>
     *
     * @param size
     *            the size in bytes
     * @return a buffer; endian is native
     */
    public ByteBuffer getBuffer( final int size )
    {
        if ( this.address == 0L )
        {
            throw new IllegalStateException();
        }

        return NativeUnsafeAccess.getBuffer( this.address, size ).order( NATIVE_ORDER );
    }

    /**
     * <p>
     *  Obtains the address of the <em>base</em> of the given buffer in memory.
     * </p>
     *
     * @param buffer
     *            the buffer
     *
     * @return the pointer to position zero in the given buffer
     *
     * @throws IllegalArgumentException
     *             if the buffer is not direct
     */
    public static Pointer fromBuffer( final Buffer buffer )
    {
        return new Pointer( DniInternal.MEMORY_ACCESSOR.getBufferPointer( buffer ) );
    }
    // TODO is this is what is expected when passing buffers into functions??
    // Above comment doesn't directly relate to this method.

    /**
     * <p>
     *  Allocates a list of pointers.
     * </p>
     * <p>
     *  The pointers are stored adjacent in memory.  The size of each pointer is
     *  determined by the platform's address size.  All pointers are initialised
     *  to {@link #NULL}.
     * </p>
     * <p>
     *  The returned list's native memory will be freed when the list is
     *  collected by the garbage collector.
     * </p>
     *
     * @param size
     *            the size (the number of pointers in the list)
     * @return a pointer list
     */
    public static NativeObjectList< Pointer > allocateList( final int size )
    {
        if ( size < 0 )
        {
            throw new IllegalArgumentException();
        }
        Pointer pointer = DniInternal.ALLOCATOR.allocate( size * NativeTypes.POINTER_SIZE );
        return DniInternal.createPointerList( pointer, size, true );
    }

    /**
     * <p>
     *  Creates a list of pointers from this pointer.
     * </p>
     * <p>
     *  The pointers are stored adjacent in memory.  The size of each pointer is
     *  determined by the platform's address size.
     * </p>
     * <p>
     *  The returned list's native memory will not be automatically freed.
     * </p>
     *
     * @param size
     *            the size (the number of pointers in the list)
     * @return a pointer list
     */
    public NativeObjectList< Pointer > getPointerList( final int size )
    {
        if ( this.address == 0L )
        {
            throw new NullPointerException();
        }
        if ( size < 0 )
        {
            throw new IllegalArgumentException();
        }
        return DniInternal.createPointerList( this, size, false );
    }

    /**
     * <p>
     *  Allocates a list of pointers.
     * </p>
     * <p>
     *  The pointers are stored adjacent in memory.  The size of each pointer is
     *  determined by the platform's address size.  All pointers are initialised
     *  to {@link #NULL}.
     * </p>
     * <p>
     *  The returned list's native memory will only be freed when the
     *  {@link Freeable#free()} method is called <em>on the list itself</em>.
     * </p>
     *
     * @param size
     *            the size (the number of pointers in the list)
     * @return a pointer list
     */
    public static NativeObjectList< Pointer > allocateLifetimeManagedList( final int size )
    {
        if ( size < 0 )
        {
            throw new IllegalArgumentException();
        }
        Pointer pointer = DniInternal.ALLOCATOR.allocate( size * NativeTypes.POINTER_SIZE );
        return DniInternal.createPointerLifetimeManagedList( pointer, size );
    }

    /**
     * <p>
     *  Creates a list of pointers from this pointer.
     * </p>
     * <p>
     *  The pointers are stored adjacent in memory.  The size of each pointer is
     *  determined by the platform's address size.
     * </p>
     * <p>
     *  The returned list's native memory will only be freed when the
     *  {@link Freeable#free()} method is called <em>on the list itself</em>.
     * </p>
     *
     * @param size
     *            the size (the number of pointers in the list)
     * @return a pointer list
     */
    public NativeObjectList< Pointer > getLifetimeManagedPointerList( final int size )
    {
        if ( this.address == 0L )
        {
            throw new NullPointerException();
        }
        if ( size < 0 )
        {
            throw new IllegalArgumentException();
        }
        return DniInternal.createPointerLifetimeManagedList( this, size );
    }

    /**
     * <p>
     *  Returns a sub-list of a list of pointers, terminated to exclude the
     *  first <code>NULL</code> pointer.
     * </p>
     * <p>
     *  By combining this method with {@link #getPointerList(int)}, it is
     *  possible to read an unbounded (or bounded to within what is expected
     *  normally) list.  For example, to safely trim a list of environment
     *  variables in Linux (see man <code>environ(3p)</code>):
     * </p>
     * <pre>
     *  &#64;{@link Library}( name = "libc.so.6" ) interface LibC
     *  {
     *      LibC INSTANCE = {@link DirectNativeInterface}.{@link DirectNativeInterface#getLibrary(Class) getLibrary}( LibC.class );
     *
     *      &#64;{@link Symbol} &#64;{@link FixedSize}( Integer.MAX_VALUE )
     *      {@link NativeObjectList}&lt; Pointer &gt; environ();
     *  }
     *
     *  {@link NativeObjectList}&lt; Pointer &gt; environ = LibC.INSTANCE.environ();
     *  environ = Pointer.trimToNullTerminator( enivron );
     * </pre>
     *
     * @param list
     *            the input list
     *
     * @return a sub-list, terminated to <code>NULL</code>
     */
    // TODO this does the location of a null once
    // Perhaps some other class / method to do dynamic null termination?
    // Or do it lazily?
    public static NativeObjectList< Pointer > trimToNullTerminator( final NativeObjectList< Pointer > list )
    {
        return list.subList( 0, list.indexOf( NULL ) );
    }

}
